package junior.db.dao.update;

import junior.db.annotation.PersistenceConfig;
import junior.db.dao.base.PersistenceAware;
import junior.db.spi.EntityPersistenceService;
import junior.util.reflect.ReflectUtils;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.Order;

import javax.persistence.GeneratedValue;
import javax.persistence.Version;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author wubo
 * @date 2024/8/29 11:21
 */
public abstract class UpdateOptionHelper {
    private static ConcurrentHashMap<Class<EntityPersistenceService>, List<EntityPersistenceService>> entityPersistenceAwareCache = new ConcurrentHashMap<>();
    private static ConcurrentHashMap<Class<?>, String[]> entityGenerateKeysCache = new ConcurrentHashMap<>();
    
    public static BatchInsertOption getBatchInsertOption(Class<?> entityClass) {
        List<EntityPersistenceService> serviceList = listEntityPersistenceAware();
        if (!serviceList.isEmpty()) {
            return serviceList.get(0).getBatchInsertOption(entityClass);
        }
        return new BatchInsertOption();
    }
    
    public static InsertOption getInsertOption(Class<?> entityClass) {
        List<EntityPersistenceService> serviceList = listEntityPersistenceAware();
        if (!serviceList.isEmpty()) {
            return serviceList.get(0).getInsertOption(entityClass);
        }
        return new InsertOption();
    }
    
    public static UpsertOption getUpsertOption(Class<?> entityClass) {
        List<EntityPersistenceService> serviceList = listEntityPersistenceAware();
        if (!serviceList.isEmpty()) {
            return serviceList.get(0).getUpsertOption(entityClass);
        }
        return new UpsertOption();
    }
    
    public static UpdateOption getUpdateOption(Class<?> entityClass) {
        List<EntityPersistenceService> serviceList = listEntityPersistenceAware();
        if (!serviceList.isEmpty()) {
            return serviceList.get(0).getUpdateOption(entityClass);
        }
        return new UpdateOption();
    }
    
    public static DeleteOption getDeleteOption(Class<?> entityClass) {
        List<EntityPersistenceService> serviceList = listEntityPersistenceAware();
        if (!serviceList.isEmpty()) {
            return serviceList.get(0).getDeleteOption(entityClass);
        }
        return new DeleteOption();
    }
    
    public static BatchUpsertOption getBatchUpsertOption(Class<?> entityClass) {
        List<EntityPersistenceService> serviceList = listEntityPersistenceAware();
        if (!serviceList.isEmpty()) {
            return serviceList.get(0).getBatchUpsertOption(entityClass);
        }
        return new BatchUpsertOption();
    }
    
    public static BatchUpdateOption getBatchUpdateOption(Class<?> entityClass) {
        List<EntityPersistenceService> serviceList = listEntityPersistenceAware();
        if (!serviceList.isEmpty()) {
            return serviceList.get(0).getBatchUpdateOption(entityClass);
        }
        return new BatchUpdateOption();
    }
    
    static void setGenerateKeys(AbstractInsertOption<?> option, Class<?> entityClass) {
        if (option.getGenerateKeyFields() == null) {
            String[] generateKeys = entityGenerateKeysCache.computeIfAbsent(entityClass, (k) -> {
                List<String> generateKeyFields = new ArrayList<>();
                for (Field field : ReflectUtils.getDeclaredFields(entityClass, null, true, false)) {
                    if (AnnotationUtils.findAnnotation(field, GeneratedValue.class) != null) {
                        generateKeyFields.add(field.getName());
                    }
                }
                for (PropertyDescriptor pd : ReflectUtils.getPropertyDescriptors(entityClass)) {
                    if (pd.getWriteMethod() != null && AnnotationUtils.findAnnotation(pd.getWriteMethod(), GeneratedValue.class) != null) {
                        generateKeyFields.add(pd.getName());
                    }
                }
                return generateKeyFields.isEmpty() ? null : generateKeyFields.toArray(new String[generateKeyFields.size()]);
            });
            if (generateKeys != null) {
                option.withGenerateKeyFields(generateKeys);
            }
        }
    }
    
    static void entityBeforeInsert(AbstractInsertOption<?> option, Object entity) {
        if (entity instanceof PersistenceAware) {
            ((PersistenceAware) entity).beforeInsert();
        }
        List<EntityPersistenceService> serviceList = listEntityPersistenceAware();
        for (EntityPersistenceService aware : serviceList) {
            aware.beforeInsert(entity);
        }
        Class<?> entityClass;
        if ((entityClass = option.getEntityClass()) == null) {
            entityClass = entity.getClass();
        }
        PersistenceConfig config = AnnotationUtils.getAnnotation(entityClass, PersistenceConfig.class);
        if (config != null) {
            setExcludeInsertFields(option, config.excludeInsertFields());
        } else {
            for (EntityPersistenceService aware : serviceList) {
                setExcludeInsertFields(option, aware.getExcludeInsertFields(entity));
            }
        }
    }
    
    static void entityBeforeUpdate(AbstractUpdateEntityOption<?> option, Object entity) {
        if (entity instanceof PersistenceAware) {
            ((PersistenceAware) entity).beforeUpdate();
        }
        List<EntityPersistenceService> serviceList = listEntityPersistenceAware();
        for (EntityPersistenceService aware : serviceList) {
            aware.beforeUpdate(entity);
        }
        Class<?> entityClass;
        if ((entityClass = option.getEntityClass()) == null) {
            entityClass = entity.getClass();
        }
        PersistenceConfig config = AnnotationUtils.getAnnotation(entityClass, PersistenceConfig.class);
        if (config != null) {
            setExcludeExcludeFields(option, config.excludeInsertFields());
        } else {
            for (EntityPersistenceService aware : serviceList) {
                setExcludeExcludeFields(option, aware.getExcludeUpdateFields(entity));
            }
        }
    }
    
    private static ConcurrentHashMap<Class<?>, List<VersionWrapper>> entityVersionsCache = new ConcurrentHashMap<>();
    
    static Map<String, Object> getVersionFields(Object entity) {
        Class<?> entityClass = entity.getClass();
        List<VersionWrapper> versionWrapperList = entityVersionsCache.computeIfAbsent(entityClass, (k) -> {
            List<VersionWrapper> list = new ArrayList<>();
            for (Field field : ReflectUtils.getDeclaredFields(entityClass, null, true, false)) {
                if (AnnotationUtils.findAnnotation(field, Version.class) != null) {
                    list.add(new VersionWrapper(field.getName(), field));
                }
            }
            for (PropertyDescriptor pd : ReflectUtils.getPropertyDescriptors(entityClass)) {
                if (pd.getWriteMethod() != null && AnnotationUtils.findAnnotation(pd.getWriteMethod(), GeneratedValue.class) != null) {
                    list.add(new VersionWrapper(pd.getName(), pd.getReadMethod()));
                }
            }
            return list;
        });
        if (versionWrapperList != null && !versionWrapperList.isEmpty()) {
            Map<String, Object> result = new HashMap<>();
            for (VersionWrapper wrapper : versionWrapperList) {
                result.put(wrapper.getName(), wrapper.getValue(entity));
            }
            return result;
        }
        return null;
    }
    
    static void setVersion(AbstractUpdateEntityOption<?> option, Object entity) {
        Class<?> entityClass = entity.getClass();
        List<VersionWrapper> versionWrapperList = entityVersionsCache.computeIfAbsent(entityClass, (k) -> {
            List<VersionWrapper> list = new ArrayList<>();
            for (Field field : ReflectUtils.getDeclaredFields(entityClass, null, true, false)) {
                if (AnnotationUtils.findAnnotation(field, Version.class) != null) {
                    list.add(new VersionWrapper(field.getName(), field));
                }
            }
            for (PropertyDescriptor pd : ReflectUtils.getPropertyDescriptors(entityClass)) {
                if (pd.getWriteMethod() != null && AnnotationUtils.findAnnotation(pd.getWriteMethod(), GeneratedValue.class) != null) {
                    list.add(new VersionWrapper(pd.getName(), pd.getReadMethod()));
                }
            }
            return list;
        });
        if (versionWrapperList != null && !versionWrapperList.isEmpty()) {
            for (VersionWrapper wrapper : versionWrapperList) {
                option.withVersion(wrapper.getName(), wrapper.getValue(entity));
            }
        }
    }
    
    static void batchSetVersion(AbstractUpdateEntityOption<?> option, Class<?> entityClass) {
        List<VersionWrapper> versionWrapperList = entityVersionsCache.computeIfAbsent(entityClass, (k) -> {
            List<VersionWrapper> list = new ArrayList<>();
            for (Field field : ReflectUtils.getDeclaredFields(entityClass, null, true, false)) {
                if (AnnotationUtils.findAnnotation(field, Version.class) != null) {
                    list.add(new VersionWrapper(field.getName(), field));
                }
            }
            for (PropertyDescriptor pd : ReflectUtils.getPropertyDescriptors(entityClass)) {
                if (pd.getWriteMethod() != null && AnnotationUtils.findAnnotation(pd.getWriteMethod(), GeneratedValue.class) != null) {
                    list.add(new VersionWrapper(pd.getName(), pd.getReadMethod()));
                }
            }
            return list;
        });
        if (versionWrapperList != null && !versionWrapperList.isEmpty()) {
            for (VersionWrapper wrapper : versionWrapperList) {
                option.withVersionField(wrapper.getName());
            }
        }
    }
    
    private static void setExcludeInsertFields(AbstractInsertOption<?> option, String[] excludeInsertFields) {
        if (excludeInsertFields != null && excludeInsertFields.length > 0) {
            option.excludeInsertFields(excludeInsertFields);
        }
    }
    
    private static void setExcludeExcludeFields(AbstractUpdateEntityOption<?> option, String[] excludeUpdateFields) {
        if (excludeUpdateFields != null && excludeUpdateFields.length > 0) {
            option.excludeUpdateFields(excludeUpdateFields);
        }
    }
    
    private static List<EntityPersistenceService> listEntityPersistenceAware() {
        return entityPersistenceAwareCache.computeIfAbsent(EntityPersistenceService.class, (k) -> listFromServiceProvider(EntityPersistenceService.class));
    }
    
    private static <T> List<T> listFromServiceProvider(Class<T> policyClass) {
        ServiceLoader<T> serviceLoader = ServiceLoader.load(policyClass);
        List<T> policyList = new ArrayList<>();
        for (T item : serviceLoader) {
            policyList.add(item);
        }
        return policyList;
    }
    
    private static <T> T getFromServiceProvider(Class<T> policyClass) {
        ServiceLoader<T> serviceLoader = ServiceLoader.load(policyClass);
        Order currentOrder = null;
        T policy = null;
        for (T item : serviceLoader) {
            Order order = item.getClass().getAnnotation(Order.class);
            if (order != null) {
                if (currentOrder != null && currentOrder.value() < order.value()) {
                    continue;
                }
                currentOrder = order;
                policy = item;
            }
            if (policy == null) {
                policy = item;
            }
        }
        return policy;
    }
    
}
